
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uart.h"
#include "os.h"
#include "stm32f10x_usart.h"
#include "device.h"

#define USART_RX_DMA_BufferSize 64

extern int device_find_by_class_port(dev_class_e class, int port);

static unsigned char USART1_RX_DMA_buffer[USART_RX_DMA_BufferSize] = {0};
static unsigned char USART2_RX_DMA_buffer[USART_RX_DMA_BufferSize] = {0};
static unsigned char USART3_RX_DMA_buffer[USART_RX_DMA_BufferSize] = {0};

struct uart_hw_config
{
    uart_port_e port;
    USART_TypeDef *USARTx;
    GPIO_TypeDef *GPIOx;
	uint16_t USARTx_TX_Pin;
	uint16_t USARTx_RX_Pin;
    DMA_Channel_TypeDef *Rx_DMA_Ch;
    uint8_t USARTx_IRQn;
    uint8_t Rx_DMA_Ch_IRQn;
    uint32_t DMA_IT_TCx;
    unsigned char *rx_dma_buffer;
};

const struct uart_hw_config uart_hw_config_list[UART_PORT_MAX] = {
    {UART_PORT0, USART1, GPIOA, GPIO_Pin_9,  GPIO_Pin_10, DMA1_Channel5, USART1_IRQn, DMA1_Channel5_IRQn, DMA1_IT_TC5, USART1_RX_DMA_buffer},
    {UART_PORT1, USART2, GPIOA, GPIO_Pin_2,  GPIO_Pin_3,  DMA1_Channel6, USART2_IRQn, DMA1_Channel6_IRQn, DMA1_IT_TC6, USART2_RX_DMA_buffer},
    {UART_PORT2, USART3, GPIOB, GPIO_Pin_10, GPIO_Pin_11, DMA1_Channel3, USART3_IRQn, DMA1_Channel3_IRQn, DMA1_IT_TC3, USART3_RX_DMA_buffer}
};

static void uart_rx_buffer_in(uart_port_e port, uint8_t data_len)
{
    if(data_len)
    {
        OS_ERR err;
        uart_t *uart = uart_find_by_port(port);
        bool rb_is_empty_before = lite_rb_is_empty(uart->rx_rb_hdl);
        lite_rb_write(uart->rx_rb_hdl, uart_hw_config_list[uart->port].rx_dma_buffer, data_len);
        if(rb_is_empty_before)
            OSSemPost((OS_SEM *)uart->rx_sem, OS_OPT_POST_1, &err);
    }
}

static void DMA_IRQHandler(uart_port_e port)
{
    OSIntEnter();

    uart_t *uart = uart_find_by_port(port);

    if(DMA_GetITStatus(uart_hw_config_list[uart->port].DMA_IT_TCx))
    {
        uint8_t data_len = USART_RX_DMA_BufferSize;
        uart_rx_buffer_in(port, data_len);
        
        DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, DISABLE);
        DMA_SetCurrDataCounter(uart_hw_config_list[uart->port].Rx_DMA_Ch, USART_RX_DMA_BufferSize);
        DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, ENABLE);
        
        DMA_ClearITPendingBit(uart_hw_config_list[uart->port].DMA_IT_TCx);
    }
    
    OSIntExit();
}

static void USART_IRQHandler(uart_port_e port)
{
    OSIntEnter();

    uart_t *uart = uart_find_by_port(port);

    if(USART_GetITStatus(uart_hw_config_list[uart->port].USARTx, USART_IT_IDLE))
    {
        USART_ReceiveData(uart_hw_config_list[uart->port].USARTx);
        
        uint8_t data_len = USART_RX_DMA_BufferSize - DMA_GetCurrDataCounter(uart_hw_config_list[uart->port].Rx_DMA_Ch);
        uart_rx_buffer_in(port, data_len);

        DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, DISABLE);
        DMA_SetCurrDataCounter(uart_hw_config_list[uart->port].Rx_DMA_Ch, USART_RX_DMA_BufferSize);
        DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, ENABLE);

        USART_ClearITPendingBit(uart_hw_config_list[uart->port].USARTx, USART_IT_IDLE);
    }
    
    OSIntExit();
}

void DMA1_Channel5_IRQHandler(void)
{
    DMA_IRQHandler(UART_PORT0);
}

void DMA1_Channel6_IRQHandler(void)
{
    DMA_IRQHandler(UART_PORT1);
}

void DMA1_Channel3_IRQHandler(void)
{
    DMA_IRQHandler(UART_PORT2);
}

void USART1_IRQHandler(void)
{
    USART_IRQHandler(UART_PORT0);
}

void USART2_IRQHandler(void)
{
    USART_IRQHandler(UART_PORT1);
}

void USART3_IRQHandler(void)
{
    USART_IRQHandler(UART_PORT2);
}

static void uart_rx_dma_init(uart_port_e port)
{
    uart_t *uart = uart_find_by_port(port);
    NVIC_InitTypeDef NVIC_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = uart_hw_config_list[uart->port].Rx_DMA_Ch_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&uart_hw_config_list[uart->port].USARTx->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart_hw_config_list[uart->port].rx_dma_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = USART_RX_DMA_BufferSize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(uart_hw_config_list[uart->port].Rx_DMA_Ch, &DMA_InitStructure);
    DMA_ITConfig(uart_hw_config_list[uart->port].Rx_DMA_Ch, DMA_IT_TC, ENABLE);
    DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, ENABLE);
}

static void uart_rx_dma_deinit(uart_port_e port)
{
    uart_t *uart = uart_find_by_port(port);

    DMA_ITConfig(uart_hw_config_list[uart->port].Rx_DMA_Ch, DMA_IT_TC, DISABLE);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = uart_hw_config_list[uart->port].Rx_DMA_Ch_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
    NVIC_Init(&NVIC_InitStructure);

    DMA_Cmd(uart_hw_config_list[uart->port].Rx_DMA_Ch, DISABLE);
    DMA_DeInit(uart_hw_config_list[uart->port].Rx_DMA_Ch);
}

static int uart_open(uart_port_e port, uart_cfg_t *config)
{
	if(!config) return -1;

    OS_ERR err;
	uart_t *uart = uart_find_by_port(port);

	uint32_t baud_rate = config->baud_rate;
	
	uint16_t word_length;
	switch(config->word_length)
	{
		case UART_WORDLENGTH_8: word_length = USART_WordLength_8b; break;
		case UART_WORDLENGTH_9: word_length = USART_WordLength_9b; break;
		default: return -1;
	}
	
	uint16_t stop_bits;
	switch(config->stop_bits)
	{
		case UART_STOPBITS_0_5: stop_bits = USART_StopBits_0_5; break;
		case UART_STOPBITS_1:   stop_bits = USART_StopBits_1;   break;
		case UART_STOPBITS_1_5: stop_bits = USART_StopBits_1_5; break;
		case UART_STOPBITS_2:   stop_bits = USART_StopBits_2;   break;
		default: return -1;
	}
	
	uint16_t parity;
	switch(config->parity)
	{
		case UART_PARITY_NO:   parity = USART_Parity_No;   break;
		case UART_PARITY_EVEN: parity = USART_Parity_Even; break;
		case UART_PARITY_ODD:  parity = USART_Parity_Odd;  break;
		default: return -1;
	}
	
	uint16_t mode;
	switch(config->mode)
	{
		case UART_MODE_RX:    mode = USART_Mode_Rx; break;
		case UART_MODE_TX:    mode = USART_Mode_Tx; break;
		case UART_MODE_RX_TX: mode = USART_Mode_Rx | USART_Mode_Tx; break;
		default: return -1;
	}
	
	uint16_t hardware_flowcontrol;
	switch(config->hardware_flowcontrol)
	{
		case UART_HWFC_None:    hardware_flowcontrol = USART_HardwareFlowControl_None; break;
		case UART_HWFC_RTS:     hardware_flowcontrol = USART_HardwareFlowControl_RTS;  break;
		case UART_HWFC_CTS:     hardware_flowcontrol = USART_HardwareFlowControl_CTS;  break;
		case UART_HWFC_RTS_CTS: hardware_flowcontrol = USART_HardwareFlowControl_RTS_CTS; break;
		default: return -1;
	}

    if(config->mode >= UART_MODE_RX)
    {
        if(!config->rx_buffer_size) return -1;

        uart->rx_rb_hdl = lite_rb_init(config->rx_buffer_size,
            (int)config->rx_buffer_size * 0.7,
            (int)config->rx_buffer_size * 0.3,
            NULL);

        if(!uart->rx_rb_hdl) return -1;

        uart->rx_sem = (int)malloc(sizeof(OS_SEM));
        OSSemCreate((OS_SEM *)uart->rx_sem, "uart_rx_sem", 0, &err);
        if(err != OS_ERR_NONE)
        {
            lite_rb_deinit(uart->rx_rb_hdl);
            return -1;
        }
    }

	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	switch(uart->port)
	{
		case UART_PORT0:
		{
		    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
		} break;

		case UART_PORT1:
		{
			RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
		} break;

		case UART_PORT2:
		{
			RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
		} break;

		default:
        {
            if(config->mode >= UART_MODE_RX)
            {
                lite_rb_deinit(uart->rx_rb_hdl);
                OSSemDel((OS_SEM *)uart->rx_sem, OS_OPT_DEL_ALWAYS, &err);
            }
            return -1;
		}
	}

    RCC_APB2PeriphClockCmd(1 << (((uint32_t)(uart_hw_config_list[uart->port].GPIOx) - (uint32_t)APB2PERIPH_BASE) >> 10), ENABLE);

	GPIO_InitStructure.GPIO_Pin = uart_hw_config_list[uart->port].USARTx_TX_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(uart_hw_config_list[uart->port].GPIOx, &GPIO_InitStructure); 
	
	GPIO_InitStructure.GPIO_Pin = uart_hw_config_list[uart->port].USARTx_RX_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(uart_hw_config_list[uart->port].GPIOx, &GPIO_InitStructure);

	USART_InitStructure.USART_BaudRate = baud_rate;
	USART_InitStructure.USART_WordLength = word_length;
	USART_InitStructure.USART_StopBits = stop_bits;
	USART_InitStructure.USART_Parity = parity;
	USART_InitStructure.USART_Mode = mode;
	USART_InitStructure.USART_HardwareFlowControl = hardware_flowcontrol;
	USART_Init(uart_hw_config_list[uart->port].USARTx, &USART_InitStructure);
    
    if(config->mode >= UART_MODE_RX)
    {
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = uart_hw_config_list[uart->port].USARTx_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);

        USART_ITConfig(uart_hw_config_list[uart->port].USARTx, USART_IT_IDLE, ENABLE);
        USART_DMACmd(uart_hw_config_list[uart->port].USARTx, USART_DMAReq_Rx, ENABLE);
        uart_rx_dma_init(port);
    }

	USART_Cmd(uart_hw_config_list[uart->port].USARTx, ENABLE);

    memcpy(&uart->cfg, config, sizeof(uart_cfg_t));

	return 0;
}

static int uart_write(uart_port_e port, uint8_t *data, int len)
{
	if(!data) return 0;
	
	uart_t *uart = uart_find_by_port(port);

	for (int i=0; i<len; i++)
	{
		while ((uart_hw_config_list[uart->port].USARTx->SR & USART_SR_TC) == RESET);
		USART_SendData(uart_hw_config_list[uart->port].USARTx, data[i]);
	}
	return len;
}

static int uart_read(uart_port_e port, uint8_t *data, int len, uint32_t timeout)
{
    OS_ERR err;
    uart_t *uart = uart_find_by_port(port);
    int nread = 0;
    
    if(uart->cfg.mode < UART_MODE_RX) return -1;

    nread = lite_rb_read(uart->rx_rb_hdl, data, len);
    if(!nread)
    {
        OSSemPend((OS_SEM *)uart->rx_sem, timeout, OS_OPT_PEND_BLOCKING, NULL, &err);
        if(err) return 0;
        nread = lite_rb_read(uart->rx_rb_hdl, data, len);
    }
    else
    {
        OSSemSet((OS_SEM *)uart->rx_sem, 0, &err);
    }
    
	return nread;
}

static void uart_close(uart_port_e port)
{
    OS_ERR err;
    uart_t *uart = uart_find_by_port(port);

    USART_ITConfig(uart_hw_config_list[uart->port].USARTx, USART_IT_IDLE, DISABLE);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = uart_hw_config_list[uart->port].USARTx_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(uart_hw_config_list[uart->port].USARTx, DISABLE);
    USART_DeInit(uart_hw_config_list[uart->port].USARTx);

    uart_rx_dma_deinit(port);
    
    if(uart->cfg.mode >= UART_MODE_RX)
    {
        lite_rb_deinit(uart->rx_rb_hdl);
        OSSemDel((OS_SEM *)uart->rx_sem, OS_OPT_DEL_ALWAYS, &err);
    }
}

static void uart_deinit(uart_port_e port)
{
    uart_t *uart = uart_find_by_port(port);
    uart_close(port);
    free(uart);
}

#define UART_OPEN_FNC(e) int e##_OPEN (uart_cfg_t *cfg) { \
    return uart_open(e, cfg); \
}

#define UART_WRITE_FNC(e) int e##_WRITE (uint8_t *data, int len) { \
    return uart_write(e, data, len); \
}

#define UART_READ_FNC(e) int e##_READ (uint8_t *data, int len, uint32_t timeout) { \
    return uart_read(e, data, len, timeout); \
}

#define UART_CLOSE_FNC(e) void e##_CLOSE (void) { \
    return uart_close(e); \
}

#define UART_DEINIT_FNC(e) void e##_DEINIT (void) { \
    return uart_deinit(e); \
}

UART_PORT_LIST(UART_OPEN_FNC)
UART_PORT_LIST(UART_WRITE_FNC)
UART_PORT_LIST(UART_READ_FNC)
UART_PORT_LIST(UART_CLOSE_FNC)
UART_PORT_LIST(UART_DEINIT_FNC)

#define UART_METHOD_PROC(e) case e: { \
    uart->open = e##_OPEN; \
    uart->write = e##_WRITE; \
    uart->read = e##_READ; \
    uart->close = e##_CLOSE; \
    uart->deinit = e##_DEINIT; \
    break; \
}

uart_t *uart_init(uart_port_e port)
{
    if(port >= UART_PORT_MAX) return NULL;
    
	uart_t *uart = malloc(sizeof(uart_t));
	if(!uart) return NULL;

	memset(uart, 0, sizeof(uart_t));

	uart->port = port;

    switch(port)
    {
        UART_PORT_LIST(UART_METHOD_PROC)
        default: break;
    }
	return uart;
}

uart_t *uart_find_by_port(uart_port_e port) {
    return (uart_t *)device_find_by_class_port(DEV_CLASS_UART, (int)port);
}
